﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.Reflection;
using System.IO;
using System.ServiceModel;
using Devtm.ServiceModel;
using Devtm.ServiceModel.Helpers;
using Devtm.ServiceModel.Proxy;
using Devtm.ServiceModel.Configurations;

namespace Devtm.ServiceModel
{


    /// <summary>
    /// factory provide custom type implement the generic interface
    /// </summary>
    public class ServiceFactory : IServiceProvider
    {

        private static ServiceFactory _instance;
        private Dictionary<Type, ProxyConfiguration> _dic = new Dictionary<Type, ProxyConfiguration>();
        private readonly object _lock = new object();
        private static object _locks = new object();
        #region Ctor

        public ServiceFactory()
        {
            
        }

        public ServiceFactory(IServiceDirectories directory)
        {
            this.Probes = directory;
        }

        static ServiceFactory()
        {            
            //_instance = new ServiceFactory();
        }

        /// <summary>
        /// Gets the instance singleton
        /// </summary>
        /// <value>The instance.</value>
        public static ServiceFactory Instance 
        { 
            get 
            { 

                if (_instance == null)
                   lock(_locks)
                       if (_instance == null)
                           _instance = new ServiceFactory();

                return _instance;

            } 
        }

        #endregion
        
        /// <summary>
        /// Registers a specific proxy for an interface.
        /// the proxy must be inherited from <see cref="Client<&ltTInterface"&lt /> and implement the same interface.
        /// the new type must override all constructor of <see cref="Client<&ltTInterface"&lt />
        /// </summary>
        /// <typeparam name="TProxy">The type of the proxy.</typeparam>
        /// <typeparam name="TInterface">The type of the interface.</typeparam>
        /// <exception cref="T:System.ArgumentException">TInterface is not an interface</exception>
        /// <exception cref="T:System.ArgumentException">TProxy is not a class</exception>
        public void Register<TProxy, TInterface>()
        {

            ProxyConfiguration retour;

            Type typeInterface = typeof(TInterface);                      
            Type typeProxy = typeof(TProxy);

            typeProxy.EnsureIsNotInterface("typeProxy");
            typeInterface.EnsureIsInterface("typeInterface");

            if (!typeProxy.GetInterfaces().Contains(typeInterface))
                throw new Exception(string.Format("{0} must implement {1}", typeProxy, typeInterface));


            if (_dic.ContainsKey(typeInterface))
                lock (_lock)
                {                    
                    retour = _dic[typeInterface];
                    retour.TypeProxy = typeProxy;
                }
            else
                lock (_lock)
                {

                    if (!_dic.ContainsKey(typeInterface))
                        _dic.Add(typeInterface, (retour = new ProxyConfiguration() { Interface = typeInterface, TypeProxy = typeProxy }));                    
                    else
                        _dic[typeInterface].TypeProxy = typeProxy;
                   
                }
            
        }

        #region Configuration

        /// <summary>
        /// Initializes a new instance wrapper that implements <see cref="T"/> using the default target endpoint from the application configuration file. 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="behaviors">The behaviors.</param>
        public ProxyConfiguration Configuration<T>(params IEndpointBehavior[] behaviors)
        {
            ProxyConfiguration box = Configuration<T>();
            box.Add(behaviors);
            return box;
        }

        #endregion

        #region GetService

        public object GetService(Type serviceType)
        {
            var s = GetConfiguration(serviceType, Script);
            return s.Create<object>(s.Resolver);
        }

        /// <summary>
        /// Get a proxy of <see cref="T" /> a endpoint of resolver
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="parameters">The parameters.</param>
        /// <returns></returns>
        public T GetService<T>(IServiceResolver resolver)
        {
            var s = Configuration<T>();
            return s.Create<T>(resolver);
        }

        /// <summary>
        /// Get a proxy of <see cref="T" /> a endpoint of the default resolver
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="parameters">The parameters.</param>
        /// <returns></returns>
        public T GetService<T>()
        {
            var s = Configuration<T>();
            return s.Create<T>(s.Resolver);
        }

        #endregion

        #region Create

        /// <summary>
        /// Creates the specified parameters.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="parameters">The parameters.</param>
        /// <returns></returns>
        public T Get<T>(IServiceConnector connector)
        {
            var s = Configuration<T>();
            return s.Create<T>(connector.Parameters());
        }

        /// <summary>
        /// Creates the specified parameters.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="parameters">The parameters.</param>
        /// <returns></returns>
        public T Get<T>(object[] parameters)
        {
            var s = Configuration<T>();
            return s.Create<T>(parameters);
        }

        /// <summary>
        /// Initializes a new instance wrapper that implements <see cref="T"/> using the default target endpoint from the application configuration file.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="behaviors">The behaviors.</param>
        /// <returns></returns>
        /// <exception cref="T:System.InvalidOperationException">Either there is no default endpoint information in the configuration file, more than one endpoint in the file, or no configuration file.</exception>
        public T Get<T>(params IEndpointBehavior[] behaviors)
        {
            var s = Configuration<T>();
            return s.Create<T>(new object[] { behaviors });
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="T:System.ServiceModel.Client`1"/> class using the default target endpoint from the application configuration file.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="configuration">The configuration.</param>
        /// <param name="endpoint">The remote address.</param>
        /// <param name="behaviors">The behaviors.</param>
        /// <returns></returns>
        /// <exception cref="T:System.InvalidOperationException">Either there is no default endpoint information in the configuration file, more than one endpoint in the file, or no configuration file.</exception>
        public T Get<T>(System.Configuration.Configuration configuration, EndpointAddress endpoint, params IEndpointBehavior[] behaviors)
        {
            var s = Configuration<T>();
            return s.Create<T>(new object[] { configuration, endpoint, behaviors });
        }

        /// <summary>Initializes a new instance wrapper that implements <see cref="T"/> using the configuration information specified in the application configuration file by <paramref name="endpointConfigurationName" />. </summary>
        /// <param name="endpointConfigurationName">The name of the endpoint in the application configuration file.</param>
        /// <exception cref="T:System.ArgumentNullException">The specified endpoint information is null.</exception>
        /// <exception cref="T:System.InvalidOperationException">The endpoint cannot be found or the endpoint contract is not valid.</exception>
        public T Get<T>(string endpointConfigurationName, params IEndpointBehavior[] behaviors)
        {
            var s = Configuration<T>();
            return s.Create<T>(new object[] { endpointConfigurationName, behaviors });
        }


        /// <summary>
        /// Initializes a new instance of the <see cref="T:System.ServiceModel.Client`1"/> class using the configuration information specified in the application configuration file by <paramref name="endpointConfigurationName"/>.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="configuration">The configuration.</param>
        /// <param name="endpointConfigurationName">The name of the endpoint in the application configuration file.</param>
        /// <param name="remoteAddress">The remote address.</param>
        /// <param name="behaviors">The behaviors.</param>
        /// <returns></returns>
        /// <exception cref="T:System.ArgumentNullException">The specified endpoint information is null.</exception>
        /// <exception cref="T:System.InvalidOperationException">The endpoint cannot be found or the endpoint contract is not valid.</exception>
        public T Get<T>(System.Configuration.Configuration configuration, string endpointConfigurationName, EndpointAddress remoteAddress, params IEndpointBehavior[] behaviors)
        {
            var s = Configuration<T>();
            return s.Create<T>(new object[] { configuration, endpointConfigurationName, remoteAddress, behaviors });
        }

        /// <summary>
        /// Initializes a new instance wrapper that implements <see cref="T"/>.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="endpointConfigurationName">The name of the endpoint in the application configuration file.</param>
        /// <param name="remoteAddress">The address of the service.</param>
        /// <param name="behaviors">The behaviors.</param>
        /// <returns></returns>
        /// <exception cref="T:System.ArgumentNullException">The endpoint is null.</exception>
        /// <exception cref="T:System.ArgumentNullException">The remote address is null.</exception>
        /// <exception cref="T:System.InvalidOperationException">The endpoint cannot be found or the endpoint contract is not valid.</exception>
        public T Get<T>(string endpointConfigurationName, string remoteAddress, params IEndpointBehavior[] behaviors)
        {
            var s = Configuration<T>();
            return s.Create<T>(new object[] { endpointConfigurationName, remoteAddress, behaviors });
        }


        /// <summary>
        /// Initializes a new instance wrapper that implements <see cref="T"/>.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="configuration">The configuration.</param>
        /// <param name="endpointConfigurationName">The name of the endpoint in the application configuration file.</param>
        /// <param name="remoteAddress">The address of the service.</param>
        /// <param name="behaviors">The behaviors.</param>
        /// <returns></returns>
        /// <exception cref="T:System.ArgumentNullException">The endpoint is null.</exception>
        /// <exception cref="T:System.ArgumentNullException">The remote address is null.</exception>
        /// <exception cref="T:System.InvalidOperationException">The endpoint cannot be found or the endpoint contract is not valid.</exception>
        public T Get<T>(System.Configuration.Configuration configuration, string endpointConfigurationName, string remoteAddress, params IEndpointBehavior[] behaviors)
        {
            var s = Configuration<T>();
            return s.Create<T>(new object[] { configuration, endpointConfigurationName, new EndpointAddress(remoteAddress), behaviors });
        }


        /// <summary>
        /// Initializes a new instance wrapper that implements <see cref="T"/> using the specified target address and endpoint information.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="endpointConfigurationName">The name of the endpoint in the application configuration file.</param>
        /// <param name="endpoint">The address of the service.</param>
        /// <param name="behaviors">The behaviors.</param>
        /// <returns></returns>
        /// <exception cref="T:System.ArgumentNullException">The endpoint is null.</exception>
        /// <exception cref="T:System.ArgumentNullException">The remote address is null.</exception>
        /// <exception cref="T:System.InvalidOperationException">The endpoint cannot be found or the endpoint contract is not valid.</exception>
        public T Get<T>(string endpointConfigurationName, EndpointAddress endpoint, params IEndpointBehavior[] behaviors)
        {
            var s = Configuration<T>();
            return s.Create<T>(new object[] { endpointConfigurationName, endpoint, behaviors });
        }

        /// <summary>
        /// Initializes a new instance wrapper that implements <see cref="T"/> using the specified binding and target address.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="binding">The binding with which to make calls to the service.</param>
        /// <param name="endpoint">The address of the service endpoint.</param>
        /// <param name="behaviors">The behaviors.</param>
        /// <returns></returns>
        /// <exception cref="T:System.ArgumentNullException">The binding is null.</exception>
        /// <exception cref="T:System.ArgumentNullException">The remote address is null.</exception>
        public T Get<T>(Binding binding, EndpointAddress endpoint, params IEndpointBehavior[] behaviors)
        {
            typeof(T).EnsureIsInterface(typeof(T).FullName);
            var s = Configuration<T>();
            return s.Create<T>(new object[] { binding, endpoint, behaviors });
        }

        /// <summary>
        /// Initializes a new instance wrapper that implements <see cref="T"/> class using the specified  <see cref="T:System.ServiceModel.Description.ServiceEndpoint"/>.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="serviceEndpoint">The endpoint for a service that allows clients to find and communicate with the service.</param>
        /// <param name="behaviors">The behaviors.</param>
        /// <returns></returns>
        public T Get<T>(ServiceEndpoint serviceEndpoint, params IEndpointBehavior[] behaviors)
        {
            var s = Configuration<T>();
            return s.Create<T>(new object[] { serviceEndpoint, behaviors });
        }

        /// <summary>
        /// Initializes a new instance wrapper that implements <see cref="T"/> using the <paramref name="callbackInstance"/> as the callback object in a duplex conversation.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="callbackInstance">The callback object that the client application uses to listen for messages from the connected service.</param>
        /// <param name="behaviors">The behaviors.</param>
        /// <returns></returns>
        /// <exception cref="T:System.ArgumentNullException">The callback instance is null.</exception>
        /// <exception cref="T:System.InvalidOperationException">Either there is no default endpoint information in the configuration file, more than one endpoint in the file, or no configuration file.</exception>
        public T Get<T>(InstanceContext callbackInstance, params IEndpointBehavior[] behaviors)
        {
            var s = Configuration<T>();
            return s.Create<T>(new object[] { callbackInstance, behaviors });
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="T:System.ServiceModel.Client`1"/> class using the <paramref name="callbackInstance"/> as the callback object in a duplex conversation.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="configuration">The configuration.</param>
        /// <param name="callbackInstance">The callback object that the client application uses to listen for messages from the connected service.</param>
        /// <param name="endpoint">The remote address.</param>
        /// <param name="behaviors">The behaviors.</param>
        /// <returns></returns>
        /// <exception cref="T:System.ArgumentNullException">The callback instance is null.</exception>
        /// <exception cref="T:System.ArgumentNullException">The remote address instance is null.</exception>
        /// <exception cref="T:System.InvalidOperationException">Either there is no default endpoint information in the configuration file, more than one endpoint in the file, or no configuration file.</exception>
        public T Get<T>(System.Configuration.Configuration configuration, InstanceContext callbackInstance, EndpointAddress endpoint, params IEndpointBehavior[] behaviors)
        {
            var s = Configuration<T>();
            return s.Create<T>(new object[] { configuration, callbackInstance, endpoint, behaviors });
        }


        /// <summary>
        /// Initializes a new instance wrapper that implements <see cref="T"/> using the specified callback service and endpoint configuration information.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="callbackInstance">The callback object that the client uses to listen for messages from the connected service.</param>
        /// <param name="endpointConfigurationName">The name of the endpoint in the application configuration file.</param>
        /// <param name="behaviors">The behaviors.</param>
        /// <returns></returns>
        /// <exception cref="T:System.ArgumentNullException">The callback instance is null.</exception>
        /// <exception cref="T:System.ArgumentNullException">The endpoint is null.</exception>
        /// <exception cref="T:System.InvalidOperationException">The endpoint cannot be found or the endpoint contract is not valid.</exception>
        public T Get<T>(InstanceContext callbackInstance, string endpointConfigurationName, params IEndpointBehavior[] behaviors)
        {
            var s = Configuration<T>();
            return s.Create<T>(new object[] { callbackInstance, endpointConfigurationName, behaviors });
        }


        /// <summary>
        /// Initializes a new instance of the <see cref="T:System.ServiceModel.Client`1"/> class using the specified callback service and endpoint configuration information.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="configuration">The configuration.</param>
        /// <param name="callbackInstance">The callback object that the client uses to listen for messages from the connected service.</param>
        /// <param name="endpoint">The remote address.</param>
        /// <param name="endpointConfigurationName">The name of the endpoint in the application configuration file.</param>
        /// <param name="behaviors">The behaviors.</param>
        /// <returns></returns>
        /// <exception cref="T:System.ArgumentNullException">The callback instance is null.</exception>
        /// <exception cref="T:System.ArgumentNullException">The endpoint is null.</exception>
        /// <exception cref="T:System.InvalidOperationException">The endpoint cannot be found or the endpoint contract is not valid.</exception>
        public T Get<T>(System.Configuration.Configuration configuration, InstanceContext callbackInstance, EndpointAddress endpoint, string endpointConfigurationName, params IEndpointBehavior[] behaviors)
        {
            var s = Configuration<T>();
            return s.Create<T>(new object[] { configuration, callbackInstance, endpoint, endpointConfigurationName, behaviors });
        }

        /// <summary>Initializes a new instance wrapper that implements <see cref="T"/>.</summary>
        /// <param name="callbackInstance">The callback object that the client uses to listen for messages from the connected service.</param>
        /// <param name="endpointConfigurationName">The name of the endpoint in the application configuration file.</param>
        /// <param name="remoteAddress">The address of the service.</param>
        /// <exception cref="T:System.ArgumentNullException">The callback instance is null.</exception>
        /// <exception cref="T:System.ArgumentNullException">The endpoint is null.</exception>
        /// <exception cref="T:System.ArgumentNullException">The remote address is null.</exception>
        /// <exception cref="T:System.InvalidOperationException">The endpoint cannot be found or the endpoint contract is not valid.</exception>
        public T Get<T>(InstanceContext callbackInstance, string endpointConfigurationName, string remoteAddress, params IEndpointBehavior[] behaviors)
        {
            var s = Configuration<T>();
            return s.Create<T>(new object[] { callbackInstance, endpointConfigurationName, remoteAddress, behaviors });
        }


        /// <summary>
        /// Initializes a new instance wrapper that implements <see cref="T"/>.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="configuration">The configuration.</param>
        /// <param name="callbackInstance">The callback object that the client uses to listen for messages from the connected service.</param>
        /// <param name="endpointConfigurationName">The name of the endpoint in the application configuration file.</param>
        /// <param name="remoteAddress">The address of the service.</param>
        /// <param name="behaviors">The behaviors.</param>
        /// <returns></returns>
        /// <exception cref="T:System.ArgumentNullException">The callback instance is null.</exception>
        /// <exception cref="T:System.ArgumentNullException">The endpoint is null.</exception>
        /// <exception cref="T:System.ArgumentNullException">The remote address is null.</exception>
        /// <exception cref="T:System.InvalidOperationException">The endpoint cannot be found or the endpoint contract is not valid.</exception>
        public T Get<T>(System.Configuration.Configuration configuration, InstanceContext callbackInstance, string endpointConfigurationName, string remoteAddress, params IEndpointBehavior[] behaviors)
        {
            var s = Configuration<T>();
            return s.Create<T>(new object[] { configuration, callbackInstance, new EndpointAddress(remoteAddress), endpointConfigurationName, behaviors });
        }


        /// <summary>Initializes a new instance wrapper that implements <see cref="T"/>.</summary>
        /// <param name="callbackInstance">The callback object that the client uses to listen for messages from the connected service.</param>
        /// <param name="endpointConfigurationName">The name of the endpoint in the application configuration file.</param>
        /// <param name="remoteAddress">The address of the service.</param>
        /// <exception cref="T:System.ArgumentNullException">The callback instance is null.</exception>
        /// <exception cref="T:System.ArgumentNullException">The endpoint is null.</exception>
        /// <exception cref="T:System.ArgumentNullException">The remote address is null.</exception>
        /// <exception cref="T:System.InvalidOperationException">The endpoint cannot be found or the endpoint contract is not valid.</exception>
        public T Get<T>(InstanceContext callbackInstance, string endpointConfigurationName, EndpointAddress remoteAddress, params IEndpointBehavior[] behaviors)
        {
            var s = Configuration<T>();
            return s.Create<T>(new object[] { callbackInstance, endpointConfigurationName, remoteAddress, behaviors });
        }

        /// <summary>
        /// Initializes a new instance wrapper that implements <see cref="T"/>.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="callbackInstance">The callback service.</param>
        /// <param name="binding">The binding with which to call the service.</param>
        /// <param name="endpoint">The address of the service endpoint.</param>
        /// <param name="behaviors">The behaviors.</param>
        /// <returns></returns>
        /// <exception cref="T:System.ArgumentNullException">The callback instance is null.</exception>
        /// <exception cref="T:System.ArgumentNullException">The binding is null.</exception>
        /// <exception cref="T:System.ArgumentNullException">The remote address is null.</exception>
        public T Get<T>(InstanceContext callbackInstance, Binding binding, EndpointAddress endpoint, params IEndpointBehavior[] behaviors)
        {
            var s = Configuration<T>();
            return s.Create<T>(new object[] { callbackInstance, binding, endpoint, behaviors });
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="T"/> class using the specified  <see cref="T:System.ServiceModel.InstanceContext"/> and  <see cref="T:System.ServiceModel.Description.ServiceEndpoint"/> objects.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="callbackInstance">The callback object that the client application uses to listen for messages from the connected service.</param>
        /// <param name="serviceEndpoint">The endpoint for a service that allows clients to find and communicate with the service.</param>
        /// <param name="behaviors">The behaviors.</param>
        /// <returns></returns>
        public T Get<T>(InstanceContext callbackInstance, ServiceEndpoint serviceEndpoint, params IEndpointBehavior[] behaviors)
        {
            var s = Configuration<T>();
            return s.Create<T>(new object[] { callbackInstance, serviceEndpoint, behaviors });
        }

        #endregion

        #region private


        [System.Diagnostics.DebuggerStepThrough]
        private ProxyConfiguration Configuration<T>()
        {
            typeof(T).EnsureIsInterface(typeof(T).FullName);
            ProxyConfiguration t = GetConfiguration(typeof(T), true);
            return t;
        }


        [System.Diagnostics.DebuggerStepThrough]
        private ProxyConfiguration GetConfiguration(Type type, bool script)
        {

            ProxyConfiguration retour;

            type.EnsureIsInterface("type");

            if (_dic.ContainsKey(type))
                retour = _dic[type];
            else
                lock (_lock)
                    if (!_dic.ContainsKey(type))
                    {
                        
                        _dic.Add(type, (retour = CreateConfiguration(type, script)));
          
                        if (Probes != null)
                            retour.Resolver = Probes.EndpointResolver(retour);            
          
                    }
                    else
                        retour = _dic[type];

            return retour;

        }


        #region Create Proxy Type

        private ProxyConfiguration CreateConfiguration(Type type, bool script)
        {
            ProxyConfiguration retour = new ProxyConfiguration() { Interface = type, Factory = this };
            return retour;
        }

        internal void CreateWrapper(ProxyConfiguration retour)
        {

            string[] a = Guid.NewGuid().ToString().Split('-');
            string assemblyName = "Custom" + retour.Interface.Name + a.Last();

            string filename = assemblyName + ".dll";

            var dir = new FileInfo(retour.Interface.Assembly.Location).Directory.FullName;

            ModuleCreator moduleCreator = new ModuleCreator
                (
                   assemblyName,
                   filename,
                   new Version(1, 0, 0, 0),
                   dir,
                   retour
                );

            retour.TypeProxy = moduleCreator.Get(typeof(Client<>).MakeGenericType(retour.Interface), retour.Interface);

        }
            
        #endregion


        private static bool _script = System.Diagnostics.Debugger.IsAttached;
        public static bool Script
        {
            get { return _script; }
            set { _script = value; }
        }

        #endregion

        public IServiceDirectories Probes { get; set; }


        #region Log
        
        internal void Log(string message, string @interface, string operationName, object[] parameters)
        {
        
        }

        internal void Log(Exception inner, MethodBase methodBase, object[] parameters)
        {

        }

        #endregion

    }

}

